package topt_12z.network;

import java.util.logging.Level;
import java.util.logging.Logger;

import topt_12z.general.EventGenerator;
import topt_12z.general.Globals;
import topt_12z.general.NetworkSynchronizer;
import topt_12z.interfaces.PacketListenerInterface;
import topt_12z.interfaces.SynchronizeEventInterface;
import topt_12z.logging.IO;
import topt_12z.statistics.SimulationStatistics;

public class Node implements PacketListenerInterface, SynchronizeEventInterface {
	private Logger log;
	private NetworkSynchronizer networkSynchronizer = null;
	private SimulationStatistics stats = null;
	private RoutingAgent routingAgent = null;

	// network coords
	private int row = -1;
	private int col = -1;

	// sending
	private Node rowNode;
	private Node colNode;

	// buffer
	private NetworkBuffer<PacketEvent> buffer = new NetworkBuffer<PacketEvent>(Globals.BUFFER_SIZE+1);
	//+1, gdyz musimy to jakos przetransportowac pomiedzy eventami otrzymania pakietu
	//i eventem synchronizowania systemu (czyli wyslaniem pakietow)
	
	public Node(int _row, int _col) throws Exception {
		row = _row;
		col = _col;
		networkSynchronizer = NetworkSynchronizer.getInstance();
		networkSynchronizer.addSynchronizeEventListener(this);
		stats = SimulationStatistics.getInstance();
		routingAgent = RoutingAgent.getInstance();
		EventGenerator.getInstance().addPacketListener(row+","+col,this);
		log=IO.getInstance().getLogger();
	}

	public Node() {
		throw new RuntimeException(
				"Cannot initialize with no arguments - Node has to have row number and col number");
	}

	@Override
	public synchronized void handleMyEvent(PacketEvent packet) {
		log.fine("Node[" + row + ", " + col + "].handleMyEvent");
		packet = updatePacket(packet);
		if(packet.getDestAddressCol() == this.col && packet.getDestAddressRow() == this.row){
			log.fine("sciezka = " + packet.getRouteRecord() + ". ilosc hopow: " + packet.getHopCounter());
			stats.addPacketToMap(packet);
		}else{
			if((networkSynchronizer.getCurrentTime()-packet.getStartTime()) >= Globals.TIMEOUT){
				//nic nie robimy - pakiet jest gubiony, bo wystapil timeout
				stats.incrementTimeoutCounter();
			}else{
				buffer.add(packet);
			}
		}
		
	}
	
	@Override
	public void handleSynchronizeEvent() {
		// routowanie pakietu z zastosowaniem odpowiedniego algorytmu
		log.finest("Entering");

		try {
			if (buffer.isEmpty()) {
				// nic nie trzeba robic - bufor jest pusty
			} else { //sprawdzenie, czy pakiety w kolejce sie nie przeterminowaly
				for (int i = 0; i < buffer.size(); i++) {
					PacketEvent packet = buffer.get(i);
					if ((networkSynchronizer.getCurrentTime() - packet.getStartTime()) >= Globals.TIMEOUT) {
						// pakiet jest usuwany, bo wystapil timeout
						buffer.remove(i);
						log.fine("Pakiet wyrzucony z bufora");
						stats.incrementTimeoutCounter();
						i--;
					} else {
						// nic nie rob - timeout nie wystapil, pakiet mozna normalnie procesowac dalej
					}
				}
				
				//mozliwe, ze wszystkie pakiety w kolejce sie przeterminowaly, wiec ponowne sprawdzenie buffer.isEmpty()
				//jesli jest pusty - to nic nie robimy, w przeciwnym przypadku zwykle przetwarzanie
				if(!buffer.isEmpty()){
					PacketEvent packet = buffer.poll(); //pobranie pakietu z HEAD'a
					Node toSend = null;
					toSend = findNode(packet); //znalezienie odpowiedniego node'a
					toSend.handleMyEvent(packet); //wyslanie pakietu
				}

			}
		} catch (Exception e) {
			log.log(Level.WARNING, "Error in node " + this.toString() + "while handlingSynchronizeEvent", e);
		}
	}
	
		//uzyj routingu - zdobadz informacje o node do ktorego ma pojsc pakiet
	private Node findNode(PacketEvent packet){
		//0,1,2
		
		String[] nodeTable = null;
		String nextNode = null;
		
		switch (Globals.ROUTING) {
		case Globals.ROUTING_LOSOWY:
			boolean route = routingAgent.randomRouting();
			if(route == true){
				return rowNode;
			}else{
				return colNode;
			}

		case Globals.ROUTING_MIN:
			nodeTable = packet.getGeneratedRoute().split(Globals.ROUTE_GENERATOR_SEPARATOR);
			nextNode = nodeTable[packet.getHopCounter()+1];
			if(rowNode.toString().equalsIgnoreCase(nextNode)){
				return rowNode;
			}else{
				return colNode;
			}
			
		case Globals.ROUTING_MAX:
			nodeTable = packet.getGeneratedRoute().split(Globals.ROUTE_GENERATOR_SEPARATOR);
			nextNode = nodeTable[packet.getHopCounter()+1];
			if(rowNode.toString().equalsIgnoreCase(nextNode)){
				return rowNode;
			}else{
				return colNode;
			}
		default:
			return null;
		}
	}

	private synchronized PacketEvent updatePacket(PacketEvent packet){
		if (packet.getRouteRecord().isEmpty()){
			//nie zwiekszaj hopow
		}
		else{
			packet.incrHopCounter();
		}
		packet.addRouteRecordHop(this.toString());
		return packet;
		
	}
	@Override
	public String toString() {
		return "[" + row + ", " + col + "]";
	}

	public Object toFullString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[" + row + ", " + col + "] --> " + rowNode.toString() + "\n");
		sb.append("\t|\n");
		sb.append("\tV\n");
		sb.append("\t" + colNode.toString() + "\n");
		return sb.toString();
	}

	public Node getRowNode() {
		return rowNode;
	}

	public void setRowNode(Node rightNode) {
		this.rowNode = rightNode;
	}

	public Node getColumnNode() {
		return colNode;
	}

	public void setColumnNode(Node botNode) {
		this.colNode = botNode;
	}

	public int getRow() {
		return row;
	}

	public void setRow(int row) {
		this.row = row;
	}

	public int getCol() {
		return col;
	}

	public void setCol(int col) {
		this.col = col;
	}

	public NetworkBuffer<PacketEvent> getBuffer() {
		return buffer;
	}
}
